/**
 * led-panel.c - LED 点阵
 * by Nixsawe <ziming_cool@126.com>
 * 2021-12-16 ~ 2021-12-16
 * 
 * 为简化 LED 点阵的输出逻辑，封装此库
 * 
 * 适用于 8*8 大小的 led 点阵控制，借助 74HC595 芯片实现功能
 * 
 * 在一瞬间，LED_PANEL_COL 控制各列是否点亮 (并行)，通过 74HC595 的翻译功能控制各行是否点亮 (串行)。
 * 故，列的刷新速度是较慢的 (我没打错字，控制对应行的点亮情况时，是在绘制某一列的内容)
 * 
 * 可能常用的方式有如下几种：
 * 1. 若显示的图形只由多条在水平方向上位置和长度相同的线组成，让列的点亮情况保持不变，用芯片动态点亮对应的行
 * 2. 类似 1，图形只由竖直方向的线组成，让行的点亮情况保持不变，操作 LED_PANEL_COL 口动态点亮对应的列
 * 3. 列扫描式：将图形按列拆分，竖直的扫描线向右水平移动，移动的过程中逐一点亮各行，也称“水平扫描”
 * 4. 行扫描式：类似列扫描式，不过是垂直方向的，也称“垂直扫描”
 * 
 * 本模块主要封装列扫描式，但封装了基础函数功能，可用于实现其他扫描方式
 */

#include "led-panel.h"

// 点阵显示缓冲区 (适用于列扫描式)
unsigned char led_panel_buffer[8];


/*----------  使用缓冲区的函数 (列扫描)  ----------*/

// 按 8*8 的 map 点亮点阵
// 要求点阵前一个下标代表行
void led_panel_write_map(unsigned char map[8][8])
{
	// 将 8*8 点阵转换成按列分割的形式，存于缓冲区中
	led_panel_make_buffer(map);

	// 显示缓冲区内容
	led_panel_print(0);
}

// 将 8*8 的 map 转换成列扫描式数据，存于缓冲区中
// 要求点阵前一个下标代表行
void led_panel_make_buffer(unsigned char map[8][8])
{
	int l, r;
	unsigned char s = 0;
	for(r = 0; r < 8; r ++)
	{
		for(l = 0, s = 0; l < 8; l ++)
		{
			s = ((s << 1) | map[l][r]);
		}
		led_panel_buffer[r] = s;
	}
}

// 扫描列点亮点阵，buffer 数组长度为 8，每个元素为一个行的点亮情况
// 当使用默认缓冲区时，传递 0 或 NULL 指针
void led_panel_print(unsigned char * buffer)
{
	int i;
	unsigned char col;

	if(!buffer)
	{
		// 使用默认缓冲区
		buffer = led_panel_buffer;
	}

	// 从最左列开始刷新，逐列扫描
	col = 0x80;
	for(i = 0; i < 8; i ++)
	{
		// 先写数值，但不立即更新
		s2p_write_data_no_edge(buffer[i]);
		// 灭掉所有的灯，避免上升沿更新数值时残影
		LED_PANEL_COL = 0xff;
		// 使用上升沿更新数值
		S2P_REDGE();
		// 点亮对应的列
		LED_PANEL_COL = ~col;	// P0 口输出低电平时才会点亮灯，故明灭情况是相反的
		col >>= 1;
	}
}

// 手动覆盖缓冲区内容
void led_panel_set_buffer(unsigned char * buffer)
{
	if(!buffer) return;
	memcpy(led_panel_buffer, buffer, sizeof(led_panel_buffer));
}

// 清空缓冲区内容
void led_panel_clr_buffer()
{
	memset(led_panel_buffer, 0, sizeof(led_panel_buffer));
}


/*----------  不使用缓冲区的函数  ----------*/

// 设置列的点亮情况
void led_panel_write_col(unsigned char col)
{
	LED_PANEL_COL = ~col;
}

// 将点亮对应的列 (设置列寄存器的对应位)
void led_panel_set_col(unsigned char col_mask)
{
	LED_PANEL_COL |= ~col_mask;
}

// 将熄灭对应的列 (设置列寄存器的对应位)
void led_panel_clr_col(unsigned char col_mask)
{
	LED_PANEL_COL &= col_mask;
}

// 设置行的点亮情况
void led_panel_write_line(unsigned char line)
{
	s2p_write_data(line);
}

// 将点亮对应的行 (设置行寄存器的对应位)
void led_panel_set_line(unsigned char line_mask)
{
	s2p_set(line_mask);
}

// 将熄灭对应的行 (设置行寄存器的对应位)
void led_panel_clr_line(unsigned char line_mask)
{
	s2p_clr(line_mask);
}

